Add necessary flags to dump optimized inlining info and fix marker placement in tinybench#83
Conversation
Add --perf-prof, --log-code, --no-log-source-code, --no-logfile-per-isolate and --logfile to getV8Flags in walltime mode. --perf-prof writes the jitdump samply uses to symbolicate JIT'd JS; --log-code writes the code log whose inlining map lets the symbolicating recover TurboFan/Maglev inlined frames the jitdump alone collapses. Source-code logging is left off to keep the log small. Refs COD-2821, COD-2822
0b43ff4 to
e07237e
Compare
Merging this PR will degrade performance by 27.74%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | fibo 30 |
19 B | 65,624 B | -99.97% |
| ❌ | Memory | fibo 15 |
5 B | 128 B | -96.09% |
| ❌ | Memory | test sync baz 100 |
1 B | 21 B | -95.24% |
| ❌ | Memory | test sync baz 100 |
1 B | 21 B | -95.24% |
| ❌ | Simulation | test_recursive_fibo_10 |
145.2 µs | 419.3 µs | -65.37% |
| ❌ | Simulation | switch 1 |
101.2 µs | 158 µs | -35.91% |
| ❌ | Simulation | one |
402.7 µs | 591.1 µs | -31.88% |
| ❌ | Memory | wait 1ms |
7 B | 10 B | -30% |
| ❌ | Simulation | switch 1 |
131.5 µs | 187.5 µs | -29.86% |
| ❌ | Simulation | wait 500ms |
12.1 ms | 17.1 ms | -29.22% |
| ❌ | Simulation | switch 1 |
130 µs | 183.1 µs | -28.99% |
| ❌ | Simulation | switch 1 |
129.8 µs | 182.7 µs | -28.98% |
| ❌ | Simulation | wait 1sec |
23 ms | 31.9 ms | -28.03% |
| ❌ | Simulation | test_recursive_fibo_10 |
146.6 µs | 197.7 µs | -25.85% |
| ❌ | WallTime | test_recursive_cached_fibo_30 |
3.4 µs | 4 µs | -15.62% |
| ❌ | Simulation | switch 2 |
11.2 µs | 12.9 µs | -13.28% |
| ❌ | WallTime | test_recursive_cached_fibo_20 |
2.7 µs | 3 µs | -11.86% |
| ❌ | WallTime | switch 1 |
276 ns | 312 ns | -11.54% |
| ❌ | Simulation | test sync baz 100 |
30.7 µs | 34.5 µs | -10.83% |
| ❌ | Simulation | test sync baz 100 |
30.6 µs | 34.3 µs | -10.67% |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing cod-2821-v8-maglevturbofan-inlining-loses-nested-inline-information (5935e1a) with main (bcd7e64)
Footnotes
-
1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports. ↩
Greptile SummaryThis PR moves the walltime instrument window from wrapping the entire
Confidence Score: 5/5Safe to merge; the hook-based measurement refactor is logically sound and the V8 flag additions are additive-only. The core change — moving the instrument window into tinybench's setup/teardown hooks — is straightforward, well-commented, and the sequential-task assumption is valid for tinybench. The V8 flag additions are guarded by the CODSPEED_V8_LOG env var and degrade gracefully to a warning if flags are missing. Open questions from earlier threads (async setup ordering, type cast on bench.opts, missing break) are pre-existing and minor. The only new item introduced here is a pinned alpha runner version in CI, which is intentional for testing but carries no runtime risk. No files require special attention beyond what was already noted in earlier review threads on packages/tinybench-plugin/src/walltime.ts. Important Files Changed
|
| const opts = this.bench.opts as { setup: Hook; teardown: Hook }; | ||
| const userSetup = opts.setup; | ||
| const userTeardown = opts.teardown; |
There was a problem hiding this comment.
If a user doesn't pass
setup/teardown to their Bench constructor, bench.opts.setup and bench.opts.teardown are typed as optional (setup?: Hook in BenchOptions). The comment asserts tinybench always resolves them to no-ops, but the explicit type cast as { setup: Hook; teardown: Hook } is a sign the compiler can't confirm this. If either value is undefined at the time installInstrumentHooks runs, calling userSetup(task, mode) or userTeardown(task, mode) will throw TypeError: X is not a function for every walltime benchmark run. Since the peerDependency spans all tinybench >=4.0.1 versions, this behaviour should not rely on an undocumented implementation detail. Nullish fallbacks cost nothing and eliminate the risk entirely.
| const opts = this.bench.opts as { setup: Hook; teardown: Hook }; | |
| const userSetup = opts.setup; | |
| const userTeardown = opts.teardown; | |
| const opts = this.bench.opts as { setup?: Hook; teardown?: Hook }; | |
| const noop: Hook = () => {}; | |
| const userSetup = opts.setup ?? noop; | |
| const userTeardown = opts.teardown ?? noop; |
| opts.setup = (task, mode) => { | ||
| const setupResult = userSetup(task, mode); | ||
| if (mode === "run") { | ||
| this.runStart = this.openInstrumentWindow(); | ||
| } | ||
| return setupResult; | ||
| }; |
There was a problem hiding this comment.
If the user supplied an async
setup hook, userSetup(task, mode) returns a Promise<void> that is captured in setupResult but not awaited before openInstrumentWindow() is called. The instrument window therefore opens while the async setup is still in flight, potentially attributing asynchronous prep time (e.g. data seeding, connection reset) to the measurement. Awaiting the user hook first keeps the marker placement accurate.
| opts.setup = (task, mode) => { | |
| const setupResult = userSetup(task, mode); | |
| if (mode === "run") { | |
| this.runStart = this.openInstrumentWindow(); | |
| } | |
| return setupResult; | |
| }; | |
| opts.setup = async (task, mode) => { | |
| await userSetup(task, mode); | |
| if (mode === "run") { | |
| this.runStart = this.openInstrumentWindow(); | |
| } | |
| }; |
| if (v8LogDir) { | ||
| flags.push( | ||
| ...[ | ||
| "--log-code", | ||
| "--no-log-source-code", | ||
| "--no-logfile-per-isolate", | ||
| `--logfile=${path.join(v8LogDir, V8_LOG_FILENAME_PATTERN)}`, | ||
| ], | ||
| ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The
walltime case is missing a break statement, unlike the analysis case above it. While harmless here since there is no subsequent case, the inconsistency can confuse future readers and will trip no-fallthrough linters.
| if (v8LogDir) { | |
| flags.push( | |
| ...[ | |
| "--log-code", | |
| "--no-log-source-code", | |
| "--no-logfile-per-isolate", | |
| `--logfile=${path.join(v8LogDir, V8_LOG_FILENAME_PATTERN)}`, | |
| ], | |
| ); | |
| } | |
| } | |
| } | |
| if (v8LogDir) { | |
| flags.push( | |
| ...[ | |
| "--log-code", | |
| "--no-log-source-code", | |
| "--no-logfile-per-isolate", | |
| `--logfile=${path.join(v8LogDir, V8_LOG_FILENAME_PATTERN)}`, | |
| ], | |
| ); | |
| } | |
| break; | |
| } | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
No description provided.